require 'pg'
require 'fox16'
#--
# coding: utf-8

class Bd
  #SERVER="192.168.110.206"
  attr_accessor :campos, :error, :mod
  attr_reader :msgerror
  ValBooleanos=%Q{true false ok ko t f FALSE TRUE OK KO T F si no SI NO S N s n}
  ValTrue=%Q{TRUE OK T true ok t SI S s si}
  ValFalse=%Q{FALSE KO F false f NO N n no}
  
  SERVER="localhost"
  PORT=5432
  #BASE_DATOS="stg"
  BASE_DATOS="empresa_02"
  #USER="stg"
  USER="dovalo"
  #PWD="padentro"
  PWD="Aobifome"
  @@con=nil
  @tabla=""
  
  SEPARADOR_CAMPO="|#"
  SEPARADOR_REGISTRO="|$"
  @msgerror="OK"
  

  @filtros   #hash con claves 'campofiltro + operador de filtro' y valores 'valor del filtro'
  @v #hash con claves 'campos, OP, tabla
  @q #cola de objetos @v
  
  def Bd.makeIcon(app,filename)
    
    filename = File.join("icons", filename)
    icon = nil
    
    File.open(filename, "rb") do |f|
      icon = FXPNGIcon.new(app, f.read)
    end
    icon
    rescue
     raise RuntimeError, "No se encuentran archivos: #{File.absolute_path(filename)} existe: #{File.exist?(filename)} leible #{File.readable?(filename)} error: #{$!}"
  end

  def Bd.selImageType(app,filename,bytes)
    extension = filename.split('.').last.upcase
    case extension
    when "GIF"
       FXGIFImage.new(app, bytes)
    when "JPG"
       FXJPGImage.new(app, bytes)
    when "PNG"
       FXPNGImage.new(app, bytes)
    else
      raise "Extension no reconocida: #{extension}" 
    end
  end
  
  def Bd.makeImg(app,filename)
    filename = File.join("icons", filename)
    image = nil
    File.open(filename, "rb") do |f|
      #image = FXJPGImage.new(app, f.read)
      image=Bd.selImageType(app,filename,f.read)
    end
    image
    rescue Exception => error # la variable error sustituye a $!
     raise RuntimeError, "No se encuentran archivo: #{File.absolute_path(filename)} existe: #{File.exist?(filename)} leible #{File.readable?(filename)} Error Tipo: #{error.class} Msg error: #{error}"
  end
  
  
=begin
  def save(campo,fila,op)
    sql="update #{@tabla} set #{campo}=$1 where id=#{n('id')[fila]};"
    puts sql
    a=[]
    a << n(campo)[fila] #valor a acutalizar
    msgerror=""
    catch(:error_bd)do
      begin
        res=@@con.exec(sql,a)
        return "OK","#{res.cmd_tuples} filas actualizadas"
      rescue Exception => error
        #raise RuntimeError, "Error al guardar #{error}"
        msgerror= "Error al guardar #{error}"
        throw :error_bd 
      end
    end
    return "error", msgerror
  end
=end


  #devuelve un hash con claves que se corresponden a filas modificadas, y valores correspondientes a los campos modificados
  # Por ejemplo si la segunda fila ha sido modificada en el campo2, y campo 8, cambios[]
  #if op=1 =>retorna el campo el campo y la fila pasada
  #if op=2 =>actualizar los campos que new y old sean distintos.
  #if op=3 => eliminar los registros
  
  def getcambios(campo,fila,op)
    cambios={}
   
      atenderafila= [:actualiza_uno,:elimina_uno].include?(op)
      nfilas=(at(@campos.keys.first,:new).length-1) || 0  #número de registros que tiene el primer campo (todos tienene el mesmo número)
      #puts "l campo es " + @campos[@campos.keys.first]['new'].length.to_s
      interval=(atenderafila ? (fila..fila):(0..nfilas)) # Si es un sólo registro solo se evalúa dicho registro. Si no, todos
      interval.each do |i| #solo los que el valor new sea distinto del new
        #puts "seleccionando cambios new=#{at(campo,:new,0)} old=#{at(campo,:old,0)}"
        changes=[].concat(@campos.keys.select{|c| at(c,:new,i)!=at(c,:old,i)}) 
        cambios[i]=changes.dup unless changes.empty?
        #en cada pos-i (de fila),hay un array con los campos modificados
      end
    #end
    
    cambios #retorna cambios
  end
  
  #devuelve la string campos ,valores, y los identificadores a los que aplica
  def getvalores_str (campo,fila,op)
    @cambios=getcambios(campo,fila,op)
    #puts "cambios=#{@cambios}"
    campos_str=""
    valores_str=""
    return if @cambios.empty?
    @id=[] #tendrá los identificadores de registro que tienen cambios
    if [:actualiza_uno,:elimina_uno].include?(op) && fila>=0 #solo operar en un registro
          @id << self.at(campo,:old,fila) 
    else #todos los ids que han sufrido cambios
         # puts at(campo,:old)
          #@id = self.at(campo,:old).select{|i| @cambios.keys.include?(i) } #si la fila i-esima del campo id está como clave en @cambios=>registro modificado)
          @cambios.each_key {|i| @id<< at(campo,:old,i)} #se guardan todos los ids apuntados por las claves en el hash @cambios
          #puts "ids=#{@id}"
    end
    
   
   
    return campos_str,valores_str unless @cambios
    
    @cambios.each do |i,campos_arr|
      if campos_arr  && !campos_arr.empty? #si hay cambios
        #puts "campos_arr #{campos_arr} i=#{i} campos_str=#{campos_str} separador_campo=#{SEPARADOR_CAMPO} SEPARADOR_REGISTRO=#{SEPARADOR_REGISTRO}"
        campos_str+=((!campos_str.empty?)? SEPARADOR_REGISTRO : "") + campos_arr.join(SEPARADOR_CAMPO) #string con los campos
        
        valores_str+=((!valores_str.empty?)? SEPARADOR_REGISTRO : "") + campos_arr.collect {|v| at(v,:new,i)}.join(SEPARADOR_CAMPO)  unless campos_arr.empty? #string con los valores de los campos
      end  
    end
    return campos_str,valores_str
  end  

  
  def save(fila,op=:actualiza_todo)
    campos_str,valores_str=getvalores_str('id',fila,op) #devuelve un hash con clave valor a actualizar
    #puts 'Los campos son ' + campos_str
    #puts 'Los valores son ' + valores_str
    #puts "campos_str=#{campos_str} fila=#{fila}"
    return "OK" if !campos_str 
    
    
    @msgerror=""
    coma_ini,coma,coma_simple,coma_fin="'","','","')"
    quote=valores_str.gsub("'","''") #duplicamos comillas simples para evitar problemas en BD (sql injection y demás)
    puts quote
    
    sql="select #{@tabla}_opt(0,0,'#{campos_str}','#{quote}','#{@id.join(',')}','#{op.to_s}')"
    puts sql

    catch(:error_bd) do
      begin
        
        res=@@con.exec(sql)
        @msgerror='OK'
        return "OK","#{res.cmd_tuples} filas actualizadas"
      rescue Exception => error
        #raise RuntimeError, "Error al guardar #{error}"
        @msgerror= "Error al guardar \n#{error}"
        #puts @msgerror
        throw :error_bd 
      end
    end
    return "error", @msgerror
  end 

  def load(fil)
  end
  
  #devuelve el campo propiedad y fila de la estructura de campos. Por ejemplo: 
  # articulo("nombre,"new",1) devuelve el registro 1, campo "nombre" y la propiedad "new"
  def at(campo,prop,fila=nil,&block)
    unless block_given?
      (!fila ? @campos[campo][prop] : @campos[campo][prop][fila])
    else
       @campos[campo][prop]= yield block unless fila
       @campos[campo][prop][fila]=yield block if fila
   end     
  end 
    
  #devuelve una matriz de valores, del campo pasado, valor new
  def n(campo) 
    @campos[campo][:new]
  end
  #devuelve una matriz de valores, del campo pasado, valor old
  def o(campo)
    @campos[campo][:old]
  end

  
  # Retorna la sql que consulta la configuración de los campos, ya sea mediante una función de BD o una consulta especial sobre 
  # pg_catalogs del postgres
  def qry_campos
    @mod=@tabla.start_with?("MOD_")
    if @mod then # se llama a una función específica de BD
      sql="select * from #{@tabla}_conf(0,0)"  # de momento le pasamos usuario y contexto 0
    else
      sql="SELECT c.column_name AS column_name, case when c.data_type like  'character%' or
           c.data_type like  'text%%' then 
           'texto' when c.data_type like  'date%' or c.data_type like  'time%' then 'Fecha' 
           else 'no texto' end as tipo 
           FROM information_schema.columns c 
           LEFT JOIN information_schema.element_types e ON 
           c.table_catalog = e.object_catalog AND 
           c.table_schema = e.object_schema AND 
           c.table_name = e.object_name AND 
           'TABLE' = e.object_type 
           Where " + (@tabla.include?('.')?"UPPER(c.table_name)=upper('#{tabla}') and upper(c.table_schema)=upper('#{esquema}')":
                   "UPPER(c.table_name) = upper('#{@tabla}')") + 
           "ORDER BY c.ordinal_position "
    end
    return sql
  end


# configura la estructura @campos de una manera estándar (haciendo uso de  consulta sql que consulta el catálogo postgres)  
  def config_campos_std (reg)
    reg.each do |row| 
        row.each do |key,value|
          if key!='tipo'  #el tipo no es un nombre de campo
            @campos[value]=Hash.new
            @campos[value][:old]=[]
            @campos[value][:new]=[]
            @campos[value][:tipo]=row['tipo']
          end
        end
    end
  end


# configura la estructura @campos de una manera convenida  (usando una función de BD que empieza por MOD_)
  def config_campos (reg)
       reg.each do |row|
          row["campos"].split("|").each_with_index do |value, key|
           # puts value, key
            @campos[value]=Hash.new
            @campos[value][:old]=[]
            @campos[value][:new]=[]
            @campos[value][:iu]=row["iu"].split("|")[key]
            @campos[value][:tipo]=row["tipo"].split("|")[key]
            @campos[value][:tabla]=row["tabla"].split("|")[key]
            @campos[value][:bsc]=row["bsc"].split("|")[key]
            @campos[value][:c_ajena]=row["c_ajena"].split("|")[key]
            @campos[value][:dependencias]=row["dependencias"].split("|")[key] 
            @campos[value][:b_exacta]=row["b_exacta"].split("|")[key]
            @campos[value][:tamanio]=row["tamanio"].split("|")[key]
         end  
       end
  end



# Inicializa la conexión con la BD, obiene la sql para obtener la configuración de los campos, y la consulta lanza dicha sql
# Rellena también la estructura que almacena los campos de la sql y sus características 
  def initialize(tabla)
    
    return (@error ="Tabla invalida") if tabla.length==0 || tabla==nil

    @tabla=tabla
     
    if !@@con
      begin 
        @@con=PGconn.connect(SERVER, PORT, '', '', BASE_DATOS, USER, PWD) #solo una conexión por clase
      rescue Exception => error
        #if @@con.status != PGconn::CONNECTION_OK
          # @error= "Error de conexion a la base de datos: #{@@con.error_message()}"
        #end
        @error="Conexion no valida a la BD Server. #{SERVER} Puerto #{PORT} Base de Datos #{BASE_DATOS} Usuario: #{USER} Password: #{PWD} Error: #{error} "
        puts "Conexion no valida a la BD Server. #{SERVER} Puerto #{PORT} Base de Datos #{BASE_DATOS} Usuario: #{USER} Password: #{PWD} Error: #{error} "
      end
    end
    
    sql=qry_campos; #obtenemos la sql para consultar los detalles de los campos 

    res=@@con.exec(sql)
    if res.num_tuples()==0
      @error= "La tabla #{tabla} no existe"
      puts @error + res.num_tuples().to_s, sql
      return @error
    end
    
    @campos=Hash.new  # @campos será una estructura de hash
    
    if @mod
      config_campos res
    else
      config_campos_std res   
    end
    #print @campos
  end


  
  def run(tabla)
    t= (tabla==nil || tabla.length==0 ? @tabla : tabla) # si no se pasa una tabla, se usará la que está inicializada 
    from=  (@mod ? t + "_bsc(0,0,'','','')" : t) # si es con el prefijo mod, se ejecuta el from con el sufijo _BSC (convenio)
    puts "from=#{from}"
    begin
      res=@@con.exec("select * from #{from} ;")
    rescue Exception => error
      @error="consulta no valida a la BD: select * from #{from} ; Error: #{error} "
      puts "consulta no valida a la BD: select * from #{from} ; Error: #{error} "
    end

    res.each do |row| 
      row.each do |key,value|
         #@campos[key]=Hash.new
         #puts "la clave es #{key} el valor: #{value} campos: #{@campos[key]['new'].class}"
        @campos[key][:new] <<((value.nil?)? nil:value.encode!('UTF-8'))
        @campos[key][:old] << ((value.nil?)? nil:value.encode!('UTF-8'))
      end
    end
  end

end

module Usuarios
  attr_accessor :usuario,:login,:permisos,:empresa,:almacenOrigen
  
  def Usuarios.validar(login,pwd)
      
  end
  def Usuarios.leepermisos()
    
  end
  
  def Usuarios.elegirusuario(marco)
    tab = FXTabBook.new(marco, :opts => LAYOUT_FILL)
    ficha_usuario = FXTabItem.new(tab, " Seleccion Usuario ")
    pagina_usuario = FXVerticalFrame.new(tab, :opts => FRAME_RAISED|LAYOUT_FILL)
    ficha_perfil = FXTabItem.new(tab, " Perfiles/Permisos ")
    pagina_perfil = FXVerticalFrame.new(tab, :opts => FRAME_RAISED|LAYOUT_FILL)
    ficha_extras = FXTabItem.new(tab, " Extras ")
    pagina_extras = FXVerticalFrame.new(tab, :opts => FRAME_RAISED|LAYOUT_FILL)
   # build_ficha_usuario(ficha_usuario)    
  end

end

